#include "DarkGDK.h"
#include "SC_Collision.h"


int numSpheres = 5;
float radius = 7.0f;
float littleRadius = 2.0f;

int rtimer = 0;
int stimer = 0;
int vtimer = 0;

float gravity = -0.1f;
float slope = 0.5f;
int ground = 1;
int jumptimer = 0;

int syncmode = 60;
int view = 1;
int hide = 0;

//player movement vector
float vx = 0;
float vy = 0;
float vz = 0;

void makeLevel()
{
	dbLoadObject( "level.x",1 );
	SC_SetupComplexObject( 1,1,2 );
	
	dbLoadImage( "1.jpg",1 );
	dbTextureObject( 1,1 );

	//add box and sphere to the level
	dbMakeObjectBox( 3,10,20,30 );
	dbPositionObject( 3,-80,30,30 );
	dbRotateObject( 3,(float)dbRnd(360),(float)dbRnd(360),(float)dbRnd(360) );
	SC_SetupObject( 3,1,2 );
	
	dbMakeObjectSphere( 4,30 );
	dbPositionObject( 4,80,30,30 );
	dbRotateObject( 4,(float)dbRnd(360),(float)dbRnd(360),(float)dbRnd(360) );
	SC_SetupObject( 4,1,1 );
	
	//all level objects are group 1
}

void makePlayer()
{
	dbMakeObjectSphere( 2,radius*2.0f );
	dbPositionObject( 2,-80,15,-20 );
	SC_SetupObject( 2,0,1 );
	
	//player has no group
}

void makeExtraSpheres( int num )
{
	for( int i = 10; i < 10 + num; i++ )
	{
		dbMakeObjectSphere( i,littleRadius*2.0f );
		dbPositionObject( i,(float)dbRnd(50)-25,(float)dbRnd(50)+100,(float)dbRnd(50)-25 );
		dbColorObject( i,dbRgb(dbRnd(255),dbRnd(255),dbRnd(255)) );
		SC_SetupObject( i,3,1 );
		SC_AllowObjectScaling( i );
	}
	
	//extra spheres are group 3
}

void handleExtraSpheres( int num )
{
	for( int i = 10; i < 10+num; i++ )
	{
		//move all the little spheres towards the player and slide them
		float oldx = dbObjectPositionX(i);
		float oldy = dbObjectPositionY(i);
		float oldz = dbObjectPositionZ(i);
		dbPointObject( i,dbObjectPositionX(2),dbObjectPositionY(2),dbObjectPositionZ(2) );
		dbMoveObject( i,0.5 );
		float x = dbObjectPositionX(i);
		float y = dbObjectPositionY(i);
		float z = dbObjectPositionZ(i);
		//little spheres collide with all (0)
		int collide = SC_SphereSlide( 0, oldx,oldy,oldz, x,y,z, littleRadius, i );

		if ( collide > 0 )
		{
			dbPositionObject( i, SC_GetCollisionSlideX(),SC_GetCollisionSlideY(),SC_GetCollisionSlideZ() );
		}
		SC_UpdateObject( i );
	}
	
	//show/hide the extra spheres
	if ( dbReturnKey() == 1 && rtimer < dbTimer() )
	{
		rtimer = dbTimer() + 300;
		hide = 1 - hide;
		
		if ( hide == 1 )
		{
			for( int i = 10; i < 10+num; i++ ) dbHideObject( i );
		}
		else
		{
			for( int i = 10; i < 10+num; i++ ) dbShowObject( i );
		}
	}
}

void movePlayer()
{
	//rotate player with mouse
	dbYRotateObject( 2,dbObjectAngleY(2) + dbMouseMoveX()/3.0f );
	dbXRotateObject( 2,dbObjectAngleX(2) + dbMouseMoveY()/3.0f );

	float oldx = dbObjectPositionX(2);
	float oldy = dbObjectPositionY(2);
	float oldz = dbObjectPositionZ(2);
	
	//apply gravity, and user changes to movement
	float angy = dbObjectAngleY(2);
	vx = 0;
	vz = 0;

	//if player is jumping or falling then apply 'normal' gravity
	//if not attempt to keep the player stuck to the floor
	if ( vy == 0 && jumptimer == 0 ) vy = vy + 10*gravity; 
	else vy = vy + gravity;

	if (dbKeyState(32) == 1 ) { vx = vx + dbCos(angy); vz = vz - dbSin(angy); }
	if (dbKeyState(30) == 1 ) { vx = vx - dbCos(angy); vz = vz + dbSin(angy); }
	if (dbKeyState(31) == 1 ) { vx = vx - dbSin(angy); vz = vz - dbCos(angy); }
	if (dbKeyState(17) == 1 ) { vx = vx + dbSin(angy); vz = vz + dbCos(angy); }

	//only jump if on ground, and a certain time after last jump
	if ( ground == 1 ) 
	{
		if ( dbSpaceKey() == 1 && jumptimer == 0 ) 
		{
			vy = vy + 3.0f;
			jumptimer = 20;
		}
	}
	
	//this would be the player's final position without collision
	float x = oldx + vx;
	float y = oldy + vy;
	float z = oldz + vz;
	
	//first handle gravity - seperated from horizontal movement
	//to achieve a more realistic effect
	//Method: simple - cast a sphere vertically down, if it hits the level then
	//                 position the object there (sticky collision) then move 
	//                 on to horizontal movement
	//  more complex - if we were to only use the simple method the player would be 
	//                 allowed to climb almost vertical slopes. Alternative is to
	//                 get the normalY direction to work out how flat the gorund
	//                 below the player is, 0-slope# is flatter, slope#-1 is steeper.
	//                 if it's flat, use sticky collision, if it's steep slide the
	//                 player down the slope. Changing slope# determines how steep the 
	//                 player can climb. NOTE: this also effects stairs.
	int collide = SC_SphereCastGroup( 1, oldx,oldy,oldz, oldx,oldy+vy,oldz, radius,0 );
	
	if ( collide > 0 )
	{
		//how flat is this ground
		float ny = SC_GetCollisionNormalY();
		if ( dbAbs(ny) > slope )
		{
			//FLAT, stick
			oldy = SC_GetStaticCollisionY();
		}
		else
		{
			//STEEP, slide
			x = x - oldx; z = z - oldz;
			oldx = SC_GetCollisionSlideX();
			oldy = SC_GetCollisionSlideY();
			oldz = SC_GetCollisionSlideZ();
			x = x + oldx; z = z + oldz;
		}
		
		//ny#<0 means the player has hit a ceiling rather than a floor
		
		if ( ny > slope )
		{
			//only on ground if standing on flat ground
			ground = 1;
			vy = 0;
		}
		else 
		{
			ground = 0;
			//if player has hit a flat ceiling then stop vy# movement
			if ( ny < -slope ) vy = gravity;
		}
	}
	else
	{
		//nothing below player, not on ground, add vertical speed to player
		oldy = oldy + vy;
		ground = 0;
	}
	
	//jumptimer will decrease only when player is back on ground
	//creates a pause between two successive jumps
	if ( ground == 1 && jumptimer > 0 ) jumptimer--;
	
	//handle horizontal movement as sliding
	//player only collides with group 1 (level) objs and moves freely through others
	collide = SC_SphereSlideGroup( 1, oldx,oldy,oldz, x,oldy,z, radius,0 );
	
	if ( collide > 0 )
	{
		//if hit, reposition player, halt movement vector
		x = SC_GetCollisionSlideX();
		oldy = SC_GetCollisionSlideY();
		z = SC_GetCollisionSlideZ();
		vx = 0;
		vz = 0;
		
		//possible code for giving the player a jumping help up stairs...
		//might be useful if slope# is set very high but stairs are still required
		//dy = oldy - SC_GetStaticCollisionY()
		//if ( dy < slope && dy > 0 && ground == 1 ) vy = 0.5;
	}
	
	//position the player
	dbPositionObject( 2,x,oldy,z );
	
	SC_UpdateObject( 2 );
}

void positionCameraToObject( int obj, int thirdPerson)
{
	dbPositionCamera( dbObjectPositionX(2),dbObjectPositionY(2),dbObjectPositionZ(2) );
	dbRotateCamera( dbObjectAngleX(2),dbObjectAngleY(2),dbObjectAngleZ(2) );
	if ( thirdPerson == 1 )
	{
		dbPitchCameraDown( 10 );
		dbMoveCamera( -30 );
	}
}

void DarkGDK( )
{
	dbSyncOn( );
	dbSyncRate( 60 );

	dbAutoCamOff( );

	dbRandomize( dbTimer() );

	SC_Start( );

	makeLevel();
	makePlayer();
	makeExtraSpheres( numSpheres );

	while( LoopGDK( ) )
	{
		handleExtraSpheres( numSpheres );
		
		movePlayer();
		
		//change view
		if ( strcmp(dbInKey(), "v") == 0 && vtimer < dbTimer() ) 
		{
			vtimer = dbTimer() + 300;
			view = 1 - view;
		}
		
		//enable/disable sync limit
		if ( strcmp(dbInKey(), "l") == 0 && stimer < dbTimer() )
		{
			stimer = dbTimer() + 300 ;
			syncmode = 60 - syncmode;
			dbSyncRate( syncmode );
		}
		
		positionCameraToObject( 2, view );
			
		dbText( 0,0,"Use W/A/S/D to move, SPACE to jump and V to change view" );
		dbText( 0,20,"Press L to enable/disable sync limiting" );
		dbText( 0,40,"Press RETURN to show/hide colored spheres" );

		char str [ 128 ];
		sprintf_s( str, 128, "FPS: %d", dbScreenFPS( ) );		dbText( 0,80,str );
		sprintf_s( str, 128, "Touching Ground: %d", ground );	dbText( 0,100,str );
		
		dbSync( );
	}
}
 